﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

using MovieFactory.Core.Primitives;
using MovieFactory.Data.Import.Loader3DS;
using MovieFactory.Data.Import.OBJLoader;
using MovieFactory.Entity.Frames;
using MovieFactory.Entity.Interface;
using MovieFactory.Entity.Scene;
using MovieFactory.RayEngine.Data;
using MovieFactory.RayEngine.IntersectionEngines;
using MovieFactory.RayEngine.PathTracer;
using MovieFactory.RayEngine.PathTracer.Volume;
using MovieFactory.RayEngine.Samplers;
using MovieFactory.Sandbox;

using InfiniteLight = MovieFactory.RayEngine.Data.InfiniteLight;
using PointLight = MovieFactory.RayEngine.Data.PointLight;

namespace MovieFactory.RayEngine {
    using MovieFactory.RayEngine.PhotonMapping;

    public class BaseRayEngine : IRenderConfig {


        public bool AreaLightAsMeshLight = true;
        public bool IsRunning { get; protected set; }

        protected internal MediumInfo Air = new MediumInfo() { IoR = 1.000277f, Attenuation = 0.1f };
        protected internal MediumInfo Glass = new MediumInfo() { IoR = 1.46f, Attenuation = 0.05f };

        protected int width, height;

        public IntersectionEngine IntersectionDevice;
        public IPathSamplerAggregator Film;
        public RenderThread[] RenderThreads;
        public RayEngineScene Scene;
        public ICamera Camera;
        public SurfaceSampler IntersectionManager;

        private Dictionary<Type, Action<IFrameElement>> frameParsers;
        private SceneGeometryInfo scene;
        private List<ILight> lights;
        private InfiniteLight envMap;
        protected MaterialInfo[] mats;

        protected IParameterBag config;

        protected int currentCamera = 0;


        public long TotalSamples {
            get { return RenderThreads.Sum(renderThread => renderThread.sampler.TotalSamples); }
        }

        public int Pass {
            get { return this.RenderThreads[0].sampler.GetPass(); }
        }

        public BaseRayEngine() {
            frameParsers = new Dictionary<Type, Action<IFrameElement>>();
            frameParsers.Add(typeof(FrameCamera), cameraItem =>
            {
                var item = (FrameCamera)cameraItem;
                Camera = new BasicPerspectiveCamera(item.Position, item.Target, item.Up, width, height) { Fov = 60 };
            });

            frameParsers.Add(typeof(FrameObjFileReference), item =>
            {
                var objFileRef = (FrameObjFileReference)item;
                var loader = new GlobalIndexObjLoader();
                scene = loader.Load(objFileRef.ObjFilePath);
                var mload = new MaterialLoader();
                mats = mload.LoadMaterials(objFileRef.MtlFilePath);

                foreach (var materialInfo in mats) {
                    if (materialInfo.Name.ToLower().Contains("glass") ||
                         materialInfo.Name.ToLower().Contains("wire_134006006")
                         ) {
                        materialInfo.Kt = materialInfo.Kd;
                        materialInfo.Kd = RgbSpectrum.ZeroSpectrum();

                        materialInfo.MediumInfo = Glass;
                    }
                    if (materialInfo.Name.ToLower().Contains("metal")) {
                        materialInfo.Kr = materialInfo.Kd;
                    }
                }
            });


            frameParsers.Add(typeof(Frame3DsFileReference), item =>
            {
                var sceneFileRef = (Frame3DsFileReference)item;
                var loader = new SceneLoader();
                scene = loader.Load(sceneFileRef.FilePath);
                mats = loader.LoadMaterials(sceneFileRef.FilePath);

                foreach (var materialInfo in mats) {
                    if (materialInfo.Name.ToLower().Contains("glass") ||
                         materialInfo.Name.ToLower().Contains("wire_134006006")
                         ) {
                        materialInfo.Kt = materialInfo.Kd;
                        materialInfo.Kd = RgbSpectrum.ZeroSpectrum();

                        materialInfo.MediumInfo = Glass;
                    }
                    if (materialInfo.Name.ToLower().Contains("metal")) {
                        materialInfo.Kr = materialInfo.Kd;
                    }
                }
                if (scene.Cameras != null && scene.Cameras.Any()) {
                    Camera = new BasicPerspectiveCamera(scene.Cameras[0].Position, scene.Cameras[0].Direction, scene.Cameras[0].Up, width, height) { Fov = scene.Cameras[0].Fov };
                }
            });


            frameParsers.Add(typeof(FrameLightsource), item =>
            {
                if (lights == null) {
                    lights = new List<ILight>();
                }
                var lightsource = (FrameLightsource)item;
                switch (lightsource.LightType) {
                    case LightSourceTypes.Point:
                        lights.Add(new PointLight(lightsource));
                        break;
                    case LightSourceTypes.EnvironmentMap:
                        lights.Add(envMap = new InfiniteLight(lightsource));
                        break;
                    case LightSourceTypes.Area:
                        //if (AreaLightAsMeshLight)
                            lights.Add(new MeshLight(lightsource));
                        break;
                }
            });
        }

        public virtual void Initialize() {
            bool lowLatency = false;
            //this.IntersectionDevice = new CpuBvhIntersectionEngine(Scene);
            this.IntersectionDevice = new OpenClIntersectionDevice(Scene, lowLatency, 0);
            
            this.RenderThreads = new RenderThread[this.RenderThreadsCount];
            var sampler = new UniformSampler() { lowLatency = lowLatency };
            this.IntersectionManager = new SurfaceSampler(Scene);
            RenderThreads[0] = 
                //new NativeRenderThread(0, (CpuBvhIntersectionEngine)IntersectionDevice, Scene, true, (ImageFilm)Film, sampler, this.IntersectionManager);
                //new DeviceRenderThread(0, IntersectionDevice, Scene, lowLatency, (ImageFilm)Film, sampler, this.IntersectionManager);
                new PhotonMappingRenderThread(0, IntersectionDevice, Scene, lowLatency, (PMFilm)Film, sampler, this.IntersectionManager);
        }

        public void OpenFrame(Frame frame) {
            this.config = frame;

            width = frame.Get<int>("ImageWidth");
            height = frame.Get<int>("ImageHeight");

            Directory.SetCurrentDirectory(frame.WorkingDir);
            foreach (var frameElement in frame.Elements.Where(frameElement => frameParsers.ContainsKey(frameElement.GetType()))) {
                frameParsers[frameElement.GetType()](frameElement);
            }

            if (Scene == null) {
                Scene = new RayEngineScene(scene, new DefaultMaterialContainer(new MaterialInfo(), mats), lights.ToArray()) {
                    DefaultLightGain = frame.Get("LightGain", new RgbSpectrum(1.75f)),
                    MaxPathDepth = frame.Get("MaxPathDepth", 15),
                    RussianRuletteImportanceCap = frame.Get("RRImportanceCap", 0.75f),
                    MaxPaths = frame.Get("PathBufferSize", 65536),
                    ShadowRayCount = frame.Get("ShadowRayCount", 1),
                    EnvironmentMap = envMap,
                    Camera = this.Camera,
                    AreaLightAsMesh = this.AreaLightAsMeshLight,
                    MatLib = new MaterialLibrary()
                };
                if (scene.Cameras != null) {
                    this.Scene.Cameras =
                        scene.Cameras.Select(
                            cameraItem =>
                            new BasicPerspectiveCamera(cameraItem.Position, cameraItem.Direction, cameraItem.Up, width,
                                                       height) {Fov = cameraItem.Fov}).Cast<ICamera>().ToList();
                }
                Scene.MatLib.Populate(this.mats);
                RenderThreadsCount = 1;
                Scene.VolumeIntegrator = new SingleScatteringIntegrator(new AABB(new Vector(-4f), new Vector(4f)),0.5f, 0.25f, RgbSpectrum.UnitSpectrum()*0.65f, RgbSpectrum.Black );
            }
            Film = new PMFilm(width, height);
                //new ImageFilm(width, height);

            this.Initialize();

        }

        public void NextCamera() {
            if (currentCamera < Scene.Cameras.Count) {
                currentCamera++;
                this.Camera = Scene.Cameras[currentCamera];
                Scene.Camera = this.Camera;
            }
        }

        public void PrevCamera() {
            if (currentCamera >0) {
                currentCamera--;
                this.Camera = Scene.Cameras[currentCamera];
                Scene.Camera = this.Camera;
            }
        }

        public int RenderThreadsCount { get; set; }
        private DateTime start;
        public void StartRenderer() {
            IsRunning = true;
            start = DateTime.UtcNow;
            for (int i = 0; i < RenderThreadsCount; i++) {
                this.RenderThreads[i].Start();
                //this.pixelDevices[i].Start();
            }
        }

        public string GetStats() {
            var totalSamples = RenderThreads.Sum(renderThread => renderThread.sampler.TotalSamples);
            var samplesPerSec = totalSamples / (float)(DateTime.UtcNow - start).TotalSeconds;

            return string.Format("{0:F5} MSamples/sec {1} Elapsed {2} Pass",
                //{0:F5} MRays/sec  this.IntersectionDevice.GetPerformance() / 1000000d, 
                samplesPerSec / 1000000d, DateTime.UtcNow - start, Pass
                //_renderThreads[0].sampler.GetPass(), 
                //Engine.GetStats()
                );
        }
        public void Save(string fn) {
            (this.Film as ImageFilm).SavePNG(fn);
        }

        public void Interrupt() {
            if (IntersectionDevice.IsRunning) {
                IntersectionDevice.Stop();
            }
            /*
            if (gpuDevice.IsRunning)
                gpuDevice.Interrupt();
            cpuDevices.ForEach(p => p.Interrupt());

             * */
            for (int i = 0; i < RenderThreadsCount; i++) {
                this.RenderThreads[i].Interrupt();
            }
//            pixelDevice.Interrupt();
        }

        #region IRenderConfig Members

        public string SavePath {
            get { return string.Empty; }
        }
        public int Width {
            get { return width; }
        }

        public int Height {
            get { return height; }
        }

        public void SaveImage(string fn) {
            this.Save(fn);
        }

        public void SaveRawImage(string fn) {
            throw new NotImplementedException();
        }

        public void Start() {
            this.StartRenderer();
        }

        public IImagePlaneComponent GetImagePlane() {
            return (this.Film);
        }


        public virtual void ConsumeJob(IRenderJob job, Action<IRenderJobResult> resultCallback) {
            throw new NotImplementedException();
        }

        #endregion
    }
}
